package cn.com.greatwall.flowable.service.impl;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.Task;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.greatwall.common.utils.DateUtil;
import cn.com.greatwall.common.utils.PageUtil;
import cn.com.greatwall.common.utils.SecurityUtils;
import cn.com.greatwall.flowable.common.CommentTypeEnum;
import cn.com.greatwall.flowable.common.constant.FlowableConstant;
import cn.com.greatwall.flowable.service.PermissionService;
import cn.com.greatwall.flowable.service.ProcessInstanceService;
import cn.com.greatwall.flowable.service.dto.CommentDto;
import cn.com.greatwall.flowable.service.dto.ProcessInstanceDetailDto;
import cn.com.greatwall.flowable.service.dto.ProcessInstanceDto;
import cn.com.greatwall.flowable.service.dto.criteria.ProcessInstanceQueryCriteria;
import cn.com.greatwall.flowable.service.mapstruct.CommentMapper;
import cn.com.greatwall.flowable.service.mapstruct.HistoricProcessInstanceMapper;
import cn.com.greatwall.flowable.service.mapstruct.ProcessInstanceDetailMapper;
import cn.com.greatwall.system.modules.security.service.dto.JwtUserDto;
import cn.com.greatwall.system.modules.system.service.dto.UserDto;
import lombok.RequiredArgsConstructor;

/**
 * @Author herw
 * @Time 2020-11-23 16:53:50
 * @Version 1.0
 * @Description: TODO(用一句话描述该文件做什么)
 */
@Service
@RequiredArgsConstructor
public class ProcessInstanceServiceImpl implements ProcessInstanceService {
    private final PermissionService permissionService;
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final cn.com.greatwall.flowable.service.TaskService taskService2;
    private final HistoryService historyService;
    private final HistoricProcessInstanceMapper historicProcessInstanceMapper;
    private final ProcessInstanceDetailMapper processInstanceDetailMapper;
    private final CommentMapper commentMapper;

    @Override
    public Map<String, Object> queryAll(ProcessInstanceQueryCriteria criteria, Pageable pageable) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceId())) {
            query.processDefinitionId(criteria.getProcessInstanceId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceName())) {
            query.processInstanceNameLike(criteria.getProcessInstanceName());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionName())) {
            query.processDefinitionName(criteria.getProcessDefinitionName());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionKey())) {
            query.processDefinitionKey(criteria.getProcessDefinitionKey());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionId())) {
            query.processDefinitionId(criteria.getProcessDefinitionId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceBusinessKey())) {
            query.processInstanceBusinessKey(criteria.getProcessInstanceBusinessKey());
        }
        if (ObjectUtils.isNotEmpty(criteria.getInvolvedUser())) {
            query.involvedUser(criteria.getInvolvedUser());
        }
        if (ObjectUtils.isNotEmpty(criteria.getFinished())) {
            if (criteria.getFinished()) {
                query.finished();
            } else {
                query.unfinished();
            }
        }
        if (ObjectUtils.isNotEmpty(criteria.getSuperProcessInstanceId())) {
            query.superProcessInstanceId(criteria.getSuperProcessInstanceId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getExcludeSubprocesses())) {
            query.excludeSubprocesses(
                    criteria.getExcludeSubprocesses() != null ? criteria.getExcludeSubprocesses() : false);
        }
        if (ObjectUtils.isNotEmpty(criteria.getFinishedAfter())) {
            query.finishedAfter(criteria.getFinishedAfter() == null ? null
                    : DateUtil.strToDateTime(
                            (criteria.getFinishedAfter() != null) ? String.valueOf(criteria.getFinishedAfter()) : ""));
        }
        if (ObjectUtils.isNotEmpty(criteria.getFinishedBefore())) {
            query.finishedBefore(
                    criteria.getFinishedBefore() == null ? null
                            : DateUtil.strToDateTime((criteria.getFinishedBefore() != null)
                                    ? String.valueOf(criteria.getFinishedBefore())
                                    : ""));
        }
        if (ObjectUtils.isNotEmpty(criteria.getStartedAfter())) {
            query.startedAfter(criteria.getStartedAfter() == null ? null
                    : DateUtil.strToDateTime(
                            (criteria.getStartedAfter() != null) ? String.valueOf(criteria.getStartedAfter()) : ""));
        }
        if (ObjectUtils.isNotEmpty(criteria.getStartedBefore())) {
            query.startedBefore(criteria.getStartedBefore() == null ? null
                    : DateUtil.strToDateTime(
                            (criteria.getStartedBefore() != null) ? String.valueOf(criteria.getStartedBefore()) : ""));
        }
        if (ObjectUtils.isNotEmpty(criteria.getStartedBy())) {
            query.startedBy(criteria.getStartedBy());
        }
        // startByMe 覆盖 startedBy
        if (ObjectUtils.isNotEmpty(criteria.getStartByMe())) {
            boolean isStartByMe = criteria.getStartByMe() != null ? criteria.getStartByMe() : false;
            if (isStartByMe) {
                query.startedBy(SecurityUtils.getCurrentUserId().toString());
            }
        }
        if (ObjectUtils.isNotEmpty(criteria.getTenantId())) {
            query.processInstanceTenantIdLike(criteria.getTenantId());
        }

        List list = query.listPage((int) pageable.getOffset(), pageable.getPageSize());

        return PageUtil.toPage(historicProcessInstanceMapper.toDto(list), query.count());
    }

    @Override
    public ProcessInstanceDetailDto findById(String processInstanceId) {
        ProcessInstanceDetailDto dto = new ProcessInstanceDetailDto();
        permissionService.validateReadPermissionOnProcessInstance(SecurityUtils.getCurrentUserId().toString(), processInstanceId);
        ProcessInstance processInstance = null;
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(processInstanceId);
        if(historicProcessInstance.getEndTime() == null) {
            processInstance = getProcessInstanceById(processInstanceId);
        }
        dto = processInstanceDetailMapper.toDto(historicProcessInstance, processInstance);
        
        return dto;
    }

    @Override
    public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance == null) {
            throw new FlowableObjectNotFoundException("No process instance found with id " + processInstanceId);
        }
        return historicProcessInstance;
    }

    @Override
    public ProcessInstance getProcessInstanceById(String processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            throw new FlowableObjectNotFoundException("No process instance found with id " + processInstanceId);
        }
        return processInstance;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void start(ProcessInstanceDto dto) {
        String processDefinitionId = StringUtils.trimToEmpty(dto.getProcessDefinitionId());
        String processDefinitionKey = StringUtils.trimToEmpty(dto.getProcessDefinitionKey());
        if (processDefinitionId.length() == 0 && processDefinitionKey.length() == 0) {
            throw new FlowableException("request param both processDefinitionId and processDefinitionKey is not found");
        } else if (processDefinitionId.length() != 0 && processDefinitionKey.length() != 0) {
            throw new FlowableException("request param both processDefinitionId and processDefinitionKey is found");
        }

        UserDto user = ((JwtUserDto) SecurityUtils.getCurrentUser()).getUser();
        String userId = user.getId().toString();

        ProcessDefinition definition = permissionService.validateReadPermissionOnProcessDefinition(userId,
                processDefinitionId, processDefinitionKey, dto.getTenantId());
        Map<String, Object> startVariables = new HashedMap<String, Object>();
        if (dto.getValues() != null && !dto.getValues().isEmpty()) {
            startVariables = dto.getValues();
        }

        Authentication.setAuthenticatedUserId(userId);

        ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder();
        processInstanceBuilder.processDefinitionId(definition.getId());
        // 流程实例标题
        processInstanceBuilder.name(user.getUsername() + definition.getName());
        // 业务key
        processInstanceBuilder.businessKey(dto.getBusinessKey());
        processInstanceBuilder.variables(startVariables);

        ProcessInstance instance = processInstanceBuilder.start();
        String processInstanceId = instance.getProcessInstanceId();
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        for (Task task : tasks) {
            // 约定：发起者节点为 __initiator__ ,则自动完成任务
            if (FlowableConstant.INITIATOR.equals(task.getTaskDefinitionKey())) {
                taskService2.addComment(task.getId(), processInstanceId, userId, CommentTypeEnum.TJ, null);
                if (ObjectUtils.isEmpty(task.getAssignee())) {
                    taskService.setAssignee(task.getId(), userId);
                }
                taskService.complete(task.getId());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String processInstanceId, boolean cascade, String reason) {
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(processInstanceId);
        if (historicProcessInstance.getEndTime() != null) {
            historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
            return;
        }
        ExecutionEntity executionEntity = (ExecutionEntity) getProcessInstanceById(processInstanceId);
        if (StringUtils.isNotEmpty(StringUtils.trimToEmpty(executionEntity.getSuperExecutionId()))) {
            throw new FlowableException("This is a subprocess");
        }
        runtimeService.deleteProcessInstance(processInstanceId, reason);
        if (cascade) {
            historyService.deleteHistoricProcessInstance(processInstanceId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void activate(String processInstanceId) {
        ProcessInstance processInstance = getProcessInstanceById(processInstanceId);
        if (!processInstance.isSuspended()) {
            throw new FlowableException("Process instance is not suspended with id " + processInstanceId);
        }
        runtimeService.activateProcessInstanceById(processInstance.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void suspend(String processInstanceId) {
        ProcessInstance processInstance = getProcessInstanceById(processInstanceId);
        if (processInstance.isSuspended()) {
            throw new FlowableException("Process instance is already suspended with id " + processInstanceId);
        }
        runtimeService.suspendProcessInstanceById(processInstance.getId());
    }

    @Override
    public List<CommentDto> comments(String processInstanceId) {
        permissionService.validateReadPermissionOnProcessInstance(SecurityUtils.getCurrentUserId().toString(),
                processInstanceId);
        List<Comment> list = taskService.getProcessInstanceComments(processInstanceId);
        Collections.reverse(list);
        
        return commentMapper.toDto(list);
    }
}
